/**
 *	\file AKTTTBasicDrawing.m
 *  
 *  \section desc_sec Description
 *  Description
 *  
 *  \if copyright
 *  	Copyright 2008 Ben Chen. All rights reserved.
 *  \endif
 *  
 *  \section hist_sec Change History:
 *  	-	1/19/08		BC		Created this file
 *  
 *  <hr>
 *  \addtogroup AnimalKingdom
 *  @{
 */

#import "AKDrawing.h"

#define AKDrawingAdding		@"adding"
#define AKDrawingMoving		@"moving"
#define AKDrawingRemoving	@"removing"

typedef struct _PieceInfo {
	AKPiece		piece;
	CALayer*	layer;
	CALayer*	reflection;
} AKPieceInfo;

enum {
	kAKDrawingTransactionIdle = 0,
	kAKDrawingTransactionBegan,
	kAKDrawingTransactionRunning,
};

// Keys for dictionaries in \p removingInfo
#define kAKDrawingRemovingAnimations	@"removingAnimations"		///< A NSMutableSet, animations for removing a piece
#define kAKDrawingRemovingKey			@"removingKey"				///< A NSString, the key for retrieving piece info from \p pieces.

// Private interface to make properties wirte-able
@interface AKDrawing ()
@property (readwrite, retain) id <AKImageProvider>		imageProvider;
@property (readwrite, retain) id <AKAnimationProvider>	animationProvider;
@property (readwrite, retain) id <AKGeometryProvider>	geometryProvider;
@property (readwrite, retain) id <AKBasicInfoProvider>	basicInfoProvider;
@property (readwrite, retain) AKView*					drawingView;
@end

@interface AKDrawing (PrivateMethods)
- (void)transactionEndAndNotifyDelegate;
- (CGRect)rectInViewForRect:(CGRect)aRect;
- (CGRect)rectForCell:(AKCellIndex)cellIndex;
#ifndef AKIPHONE
- (void)setToStickToSuperLayerForLayer:(CALayer*)aLayer;
- (CIFilter*)filterForActivation;
#endif
@end

@implementation AKDrawing

@synthesize imageProvider, animationProvider, geometryProvider, basicInfoProvider, drawingView, delegate;

#pragma mark -
#pragma mark Layout Functions for pieces and reflection layer

- (void)layoutSublayersOfLayer:(CALayer *)self_layer {
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions
	
	NSEnumerator *enumerator = [pieces objectEnumerator];
	NSValue *pieceValue;
	while (nil != (pieceValue = [enumerator nextObject])) {
		AKPieceInfo pieceInfo;
		[pieceValue getValue:&pieceInfo];
		
		CGRect frameRect	= [self rectForCell:(pieceInfo.piece).cell];
		
		if (self_layer == piecesLayer) {
			(pieceInfo.layer).frame		= frameRect;
		} else if (self_layer == reflectionLayer) {
			(pieceInfo.reflection).frame	= frameRect;
		}
	}
}

#pragma mark -
#pragma mark Initialization

- (id)initWithImageProvider:(id <AKImageProvider>)anImageProvider 
		  animationProvider:(id <AKAnimationProvider>)anAnimationProvider
		   geometryProvider:(id <AKGeometryProvider>)aGeometryProvider
		  basicInfoProvider:(id <AKBasicInfoProvider>)aBasicInfoProvider
				   drawView:(AKView*)aDrawView {
	self = [super init];
	
	if (self) {
		// Save dependencies
		self.imageProvider		= anImageProvider;
		self.animationProvider	= anAnimationProvider;
		self.geometryProvider	= aGeometryProvider;
		self.basicInfoProvider	= aBasicInfoProvider; 
		self.drawingView		= aDrawView;
		
		// Created animation for activation/deactivation
		zeroNumber	= [[NSNumber alloc] initWithFloat:0.0];
		oneNumber	= [[NSNumber alloc] initWithFloat:1.0];
#ifndef AKIPHONE
		activationAnimation		= [CABasicAnimation animationWithKeyPath:@"filters.pulseFilter.inputIntensity"];
		deactivationAnimation	= [CABasicAnimation animationWithKeyPath:@"filters.pulseFilter.inputIntensity"];
#else 
		activationAnimation		= [CABasicAnimation animationWithKeyPath:@"bounds.size"];
		deactivationAnimation	= [CABasicAnimation animationWithKeyPath:@"bounds.size"];
#endif
		
		activationAnimation.fromValue	= zeroNumber;
		activationAnimation.toValue		= oneNumber;
		activationAnimation.duration	= 0.25;
		activationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
		activationAnimation.removedOnCompletion = YES;
		
		deactivationAnimation.fromValue = oneNumber;
		deactivationAnimation.toValue	= zeroNumber;
		deactivationAnimation.duration	= 0.25;
		deactivationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
		deactivationAnimation.removedOnCompletion = YES;
		
		[activationAnimation retain];
		[deactivationAnimation retain];		
		
		// Dictionary for saving PieceInfos
		pieces = [[NSMutableDictionary alloc] init];
		
		// Layers
		[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions
		
		// Create a Container Layer to hold all layers
		containerLayer = [CALayer layer];
#ifndef AKIPHONE
		containerLayer.layoutManager = [CAConstraintLayoutManager layoutManager];
#endif
		
		// Create a Layer for backgound
		background = [CALayer layer];
		background.contents = (id)[imageProvider backgroundImage];
		background.bounds = containerLayer.bounds;
#ifndef AKIPHONE
		[self setToStickToSuperLayerForLayer:background];
#endif
		
		// Create a Layer for reflections (if necessary)
		reflectionLayer = [CALayer layer];
		reflectionLayer.name = @"reflectionLayer";
#ifndef AKIPHONE
		[self setToStickToSuperLayerForLayer:reflectionLayer];
		reflectionLayer.layoutManager = self;
#endif
		
		// Create a Layer for pieces
		piecesLayer = [CALayer layer];
		piecesLayer.name = @"piecesLayer";
#ifndef AKIPHONE
		[self setToStickToSuperLayerForLayer:piecesLayer];
		piecesLayer.layoutManager = self;
#endif
		// Add all to container layer
		containerLayer.sublayers = [NSArray arrayWithObjects:background, reflectionLayer, piecesLayer, nil];
		
		// Add layer to the view
#ifndef AKIPHONE
		[drawingView setLayer:containerLayer];
		[drawingView setWantsLayer:YES];
#else
		[drawingView.layer addSublayer:containerLayer];
		containerLayer.frame	= drawingView.layer.frame;
		CGRect bakRect = CGRectZero;
		bakRect.size		= [geometryProvider chessboardSize];
		background.frame	= [self rectInViewForRect:bakRect];
#endif
		transactionState	= kAKDrawingTransactionIdle;
		removingInfoSet		= [[NSMutableSet alloc] init];
	}
	
	return self;
}		

- (void)dealloc {	
	[zeroNumber release];
	[oneNumber release];
	
	[activationAnimation release];
	[deactivationAnimation release];
	
	[imageProvider release];
	[animationProvider release];
	[geometryProvider release];
	[drawingView release];
	
	[pieces release];
	[removingInfoSet release];
	
	[super dealloc];
}

#pragma mark -
#pragma mark Basic Drawing

- (void)removeAllPieces {
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions

	for (NSString *key in pieces) {
		NSValue *pieceValue = [pieces valueForKey:key];
		AKPieceInfo pieceInfo;
		[pieceValue getValue:&pieceInfo];
//		[self removePiece:pieceInfo.piece];
		[pieceInfo.layer removeFromSuperlayer];
		[pieceInfo.reflection removeFromSuperlayer];
	}
	
	[pieces removeAllObjects];
	
	activatedPiece = nil;
}

- (void)addPiece:(AKPiece)piece {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSValue *pieceValue = [pieces valueForKey:pieceKey];
	
	if (pieceValue)
		// same piece cannot be added twice
		[NSException raise:NSInvalidArgumentException format:[NSString stringWithFormat:@"addPiece: piece:%@ already exists", pieceKey]];

	CGRect frameRect	= [self rectForCell:piece.cell];
	
	// Create a filter for activation animation
#ifndef AKIPHONE
	NSArray *filterArray = [NSArray arrayWithObject:[self filterForActivation]];
#endif
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions
	
	// Create a new layer for the piece and set values
	CALayer* newPiece	= [CALayer layer];
	newPiece.contents	= (id)[imageProvider imageForPiece:piece];
	newPiece.frame		= frameRect;
	newPiece.name		= pieceKey;	// layer will later be retrieved by name
#ifndef AKIPHONE
	newPiece.filters	= filterArray;
#endif
	newPiece.opacity	= 1.0;
	newPiece.zPosition	= (CGFloat)piece.cell;	// The bigger the cell index, the closer the piece
	
	// Get animation from animation provider and add it to the layer
	CAAnimation *animationForNewPiece	= [animationProvider animationForEvent:kAKEventPieceCreated	piece:piece	info:[NSDictionary dictionaryWithObject:newPiece forKey:kAKAnimationProviderLayerKey]];	
	animationForNewPiece.delegate		= self;
	[newPiece addAnimation:animationForNewPiece forKey:AKDrawingAdding];
	if (transactionState == kAKDrawingTransactionBegan)		// Add to transaction record if necessary
		[animationsInTransaction addObject:[newPiece animationForKey:AKDrawingAdding]];
	
	[piecesLayer addSublayer:newPiece];
		
	// Do the same for the reflection if image provider provides it
	CALayer* reflection = nil;
	if ([imageProvider respondsToSelector:@selector(reflectionForPiece:)]) {
		reflection = [CALayer layer];
		reflection.contents	= (id)[imageProvider reflectionForPiece:piece];
		reflection.frame	= frameRect;
		reflection.opacity	= 0.2;
		reflection.name		= pieceKey;
#ifndef AKIPHONE
		reflection.filters	= filterArray;
#endif
		newPiece.zPosition	= -1.0 * (CGFloat)piece.cell;
		
//		CAAnimation *animationForRef	= [animationProvider emergingAnimationForPiece:piece pieceLayer:reflection];
		CAAnimation *animationForRef = [animationProvider animationForEvent:kAKEventPieceCreated piece:piece info:[NSDictionary dictionaryWithObject:reflection forKey:kAKAnimationProviderLayerKey]];
		animationForRef.delegate		= self;
		[reflection addAnimation:animationForRef forKey:AKDrawingAdding];
		if (transactionState == kAKDrawingTransactionBegan)
			[animationsInTransaction addObject:[reflection animationForKey:AKDrawingAdding]];
		
		[reflectionLayer addSublayer:reflection];
	}
		
	// Save the piece info for looking up later
	AKPieceInfo pieceInfo;
	pieceInfo.piece			= piece;
	pieceInfo.layer			= newPiece;
	pieceInfo.reflection	= reflection;
	
	NSValue *value = [NSValue valueWithBytes:&pieceInfo objCType:@encode(AKPieceInfo)];
	[pieces setValue:value forKey:pieceKey];
}

- (void)movePiece:(AKPiece)piece
		   toCell:(AKCellIndex)dstCell {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSValue *pieceValue = [pieces valueForKey:pieceKey];
	if (!pieceValue) {
		[NSException raise:NSInvalidArgumentException format:[NSString stringWithFormat:@"movePiece:toCell: piece:%@ cannot be found", pieceKey]];
	}

	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions
	
	CGRect newFrame = [self rectForCell:dstCell];
	
	AKPieceInfo pieceInfo;
	[pieceValue getValue:&pieceInfo];
	
	// Save the starting poz
	CGRect srcRect = pieceInfo.layer.frame;
	
	// Set the end properties
	pieceInfo.piece.cell		= dstCell;
	pieceInfo.layer.frame		= newFrame;
	pieceInfo.layer.zPosition	= (CGFloat)dstCell;
	if (pieceInfo.reflection) {
		pieceInfo.reflection.frame		= newFrame;
		pieceInfo.reflection.zPosition	= -1.0 * (CGFloat)dstCell;
	}
	
	CAAnimation *movingAnimation = [animationProvider animationForEvent:kAKEventPieceMoved 
																  piece:pieceInfo.piece
																   info:[NSDictionary dictionaryWithObjectsAndKeys:pieceInfo.layer, kAKAnimationProviderLayerKey, [NSValue valueWithCGRect:(srcRect)], kAKAnimationProviderStartRectKey, nil]];

	movingAnimation.delegate = self;
	[(pieceInfo.layer) addAnimation:movingAnimation forKey:AKDrawingMoving];
	if (transactionState == kAKDrawingTransactionBegan)
		[animationsInTransaction addObject:[pieceInfo.layer animationForKey:AKDrawingMoving]];
	
	if (pieceInfo.reflection) {
		CAAnimation *movingAnimationRef = [animationProvider animationForEvent:kAKEventPieceMoved
																		 piece:pieceInfo.piece
																		  info:[NSDictionary dictionaryWithObjectsAndKeys:pieceInfo.layer, kAKAnimationProviderLayerKey, [NSValue valueWithCGRect:(srcRect)], kAKAnimationProviderStartRectKey, nil]];

		movingAnimationRef.delegate = self;
		[(pieceInfo.reflection) addAnimation:movingAnimationRef forKey:AKDrawingMoving];
		if (transactionState == kAKDrawingTransactionBegan)
			[animationsInTransaction addObject:[pieceInfo.reflection animationForKey:AKDrawingMoving]];
	}
		
	// Update the piece data
	NSString *newKey				= AKStringFromPiece(pieceInfo.piece);
	(pieceInfo.layer).name			= newKey;
	(pieceInfo.reflection).name		= newKey;
	[pieces removeObjectForKey:pieceKey];
	[pieces setValue:[NSValue valueWithBytes:&pieceInfo objCType:@encode(AKPieceInfo)] forKey:newKey];
}

- (void)removePiece:(AKPiece)piece {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSValue *pieceValue = [pieces valueForKey:pieceKey];
	
	if (!pieceValue)
		[NSException raise:NSInvalidArgumentException format:@"removePiece: piece:%@ doesn't exist", pieceKey];
		
	// Saving removal information
	NSMutableDictionary	*removalInfo = [[[NSMutableDictionary alloc] init] autorelease];
	NSMutableSet		*animations	= [NSMutableSet set];
	[removalInfo setValue:pieceKey forKey:kAKDrawingRemovingKey];
	[removalInfo setValue:animations forKey:kAKDrawingRemovingAnimations];
	[removingInfoSet addObject:removalInfo];
	
	AKPieceInfo pieceInfo; 
	[pieceValue getValue:&pieceInfo];	
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];	// disable actions
	
	CAAnimation *removalAni = [animationProvider animationForEvent:kAKEventPieceEliminated piece:piece info:[NSDictionary dictionaryWithObject:pieceInfo.layer forKey:kAKAnimationProviderLayerKey]];

	removalAni.delegate = self;
	[pieceInfo.layer addAnimation:removalAni forKey:AKDrawingRemoving];
	if (transactionState == kAKDrawingTransactionBegan) {
		id animationToAdd = [pieceInfo.layer animationForKey:AKDrawingRemoving];
		[animationsInTransaction addObject:animationToAdd];
		[animations addObject:animationToAdd];
	}
	
	if (pieceInfo.reflection) {
		CAAnimation *removalAniRef = [animationProvider animationForEvent:kAKEventPieceEliminated piece:piece info:[NSDictionary dictionaryWithObject:pieceInfo.reflection forKey:kAKAnimationProviderLayerKey]];

		removalAniRef.delegate = self;
		[pieceInfo.reflection addAnimation:removalAniRef forKey:AKDrawingRemoving];
		if (transactionState == kAKDrawingTransactionBegan) {
			id animationToAdd = [pieceInfo.reflection animationForKey:AKDrawingRemoving];
			[animationsInTransaction addObject:animationToAdd];
			[animations addObject:animationToAdd];
		}
	}
	
	// Set final values
	pieceInfo.layer.opacity = 0.0;
	
	// Removing of the layers is done in the delegate method
}

#pragma mark -
#pragma mark Delegate method

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
	if (transactionState == kAKDrawingTransactionRunning) {
		// Remove piece layers when the elimination animation finishes
		if ([removingInfoSet count]) {
			NSMutableSet *dictsToRemove = [NSMutableSet set];
			for (NSDictionary *dict in removingInfoSet) {
				NSMutableSet *removalAnimations = [dict valueForKey:kAKDrawingRemovingAnimations];
				if ([removalAnimations containsObject:theAnimation]) {
					if ([removalAnimations count] == 1) {
						// Remove the piece layers
						NSString *pieceKey = [dict valueForKey:kAKDrawingRemovingKey];
						NSValue *pieceValue = [pieces valueForKey:pieceKey];
						AKPieceInfo pieceInfo; 
						[pieceValue getValue:&pieceInfo];
						[pieceInfo.layer removeFromSuperlayer];
						[pieceInfo.reflection removeFromSuperlayer];
						[pieces removeObjectForKey:pieceKey];
					}
					[removalAnimations removeObject:theAnimation];
					// Remove from the set if necessary
					if ([removalAnimations count] == 0)
//						[removingInfoSet removeObject:dict];
						[dictsToRemove addObject:dict];
				}
			}
			[removingInfoSet minusSet:dictsToRemove];
		}
		
		if ([animationsInTransaction containsObject:theAnimation]) {
			[animationsInTransaction removeObject:theAnimation];
			if ([animationsInTransaction count] == 0)
				[self performSelectorOnMainThread:@selector(transactionEndAndNotifyDelegate) withObject:nil waitUntilDone:NO];
		}
	}
}

#pragma mark -
#pragma mark Interactive Drawing

- (AKCellIndex)cellIndexForPoint:(CGPoint)position {
	AKCellIndex retVal = NSNotFound;
	
	unsigned i;
	for (i = 0; i < basicInfoProvider.numOfCells; ++i) {
		CGRect rectToDraw = [self rectInViewForRect:[geometryProvider rectForCell:i]];
		if (CGRectContainsPoint(rectToDraw, position)) {
			retVal = i;
			break;
		}
	}
	
	return retVal;
}

- (void)activatePiece:(AKPiece)piece {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSValue *pieceValue = [pieces valueForKey:pieceKey];
	
	if (!pieceValue) {
		[NSException raise:NSInvalidArgumentException format:[NSString stringWithFormat:@"activatePiece: piece:%@ cannot be found", pieceKey]]; 
	}
	
	if (!(activatedPiece && activatedPiece == pieceValue)) {
		// if the piece has not been activated already
		if (activatedPiece && activatedPiece != pieceValue)
			// if there's another piece activated, deactivate it first
			[self deactivatePiece];
		
		if (pieceValue) {
			AKPieceInfo pieceInfo;
			[pieceValue getValue:&pieceInfo];
			
			[(pieceInfo.layer) addAnimation:activationAnimation forKey:@"activationAnimation"];
			[(pieceInfo.reflection) addAnimation:activationAnimation forKey:@"activationAnimation"];
			
			// Set end values
#ifndef AKIPHONE
			[(pieceInfo.layer) setValue:oneNumber
							 forKeyPath:@"filters.pulseFilter.inputIntensity"];
			[(pieceInfo.layer) setValue:oneNumber
								 forKey:@"filters.pulseFilter.inputIntensity"];
#else
			activatedPieceOriSize = (pieceInfo.layer).bounds.size;
			activatedPieceOriRefSize = (pieceInfo.reflection).bounds.size;
			
			[(pieceInfo.layer) setValue:[NSValue valueWithCGSize:CGSizeMake(activatedPieceOriSize.width * 1.5, activatedPieceOriSize.height * 1.5)]
							  forKeyPath:@"bounds.size"];
			[(pieceInfo.layer) setValue:[NSValue valueWithCGSize:CGSizeMake(activatedPieceOriRefSize.width * 1.5, activatedPieceOriRefSize.height * 1.5)]
								  forKey:@"bounds.size"];
#endif			
			activatedPiece = pieceValue;
		}
	}
}

- (void)deactivatePiece {
	if (activatedPiece) {
		AKPieceInfo pieceInfo;
		[activatedPiece getValue:&pieceInfo];
		
		[(pieceInfo.layer)		addAnimation:deactivationAnimation forKey:@"deactivationAnimation"];
		[(pieceInfo.reflection)	addAnimation:deactivationAnimation forKey:@"deactivationAnimation"];
		
		// Set end values
#ifndef AKIPHONE
		[(pieceInfo.layer) setValue:zeroNumber
						 forKeyPath:@"filters.pulseFilter.inputIntensity"];
		[(pieceInfo.layer) setValue:zeroNumber
							 forKey:@"filters.pulseFilter.inputIntensity"];
#else
		[(pieceInfo.layer) setValue:[NSValue valueWithCGSize:(activatedPieceOriSize)]
						 forKeyPath:@"bounds.size"];
		[(pieceInfo.layer) setValue:[NSValue valueWithCGSize:(activatedPieceOriRefSize)]
							 forKey:@"bounds.size"];
#endif		
		activatedPiece = nil;
	}
}

- (void)aboutToDragPiece:(AKPiece)piece atPoint:(CGPoint)aPoint {
	NSString *pieceKey = AKStringFromPiece(piece);
	NSValue *pieceValue = [pieces valueForKey:pieceKey];
	
	if (!pieceValue) {
		[NSException raise:NSInvalidArgumentException format:[NSString stringWithFormat:@"aboutToDragPiece:atPoint: piece:%@ cannot be found.", pieceKey]];
	}
	
	draggedValue = pieceValue;
	AKPieceInfo pieceInfo;
	[pieceValue getValue:&pieceInfo];
	
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; // disable actions
	
	// Create a new layer for dragging
	CALayer *thePiece = pieceInfo.layer;
	if (!draggedPiece)
		draggedPiece = [CALayer layer];
	draggedPiece.contents	= thePiece.contents;
	draggedPiece.frame		= thePiece.frame;
	draggedPiece.opacity	= 0.5;
	//draggedPiece.delegate	= noAnimationDelegate; // no animation
	draggedPiece.zPosition	= (CGFloat)(basicInfoProvider.numOfCells + 1);
	[piecesLayer addSublayer:draggedPiece];
	
	if (pieceInfo.reflection != nil) {
		CALayer *theReflection = pieceInfo.reflection;
		if (!draggedReflection)
			draggedReflection = [CALayer layer];
		draggedReflection.contents  = theReflection.contents;
		draggedReflection.frame		= theReflection.frame;
		draggedReflection.opacity	= 0.2;
		//draggedReflection.delegate	= noAnimationDelegate; // no animation
		draggedReflection.zPosition	= -1.0 * (CGFloat)(basicInfoProvider.numOfCells + 1);
		[reflectionLayer addSublayer:draggedReflection];
	}
}

- (void)dragPieceAt:(CGPoint)position {	
	[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions]; // disable actions
	
	draggedPiece.position = position;
	
	if (draggedReflection) {
		draggedReflection.position = position; 
	}
}

- (void)stopDraggingShouldComeBack:(BOOL)flag {
	AKPieceInfo pieceInfo;
	[draggedValue getValue:&pieceInfo];
	
	if (flag) {
		draggedPiece.delegate	= nil;	// get default animation back
		draggedPiece.frame		= (pieceInfo.layer).frame;
	}
	draggedPiece.opacity	= 0.0;
	
	if (draggedReflection) {
		if (flag) {
			draggedReflection.delegate	= nil; // get default animation back
			draggedReflection.frame		= (pieceInfo.reflection).frame;
		}
		draggedReflection.opacity = 0.0;
	}
	
	draggedValue = nil;
	
	[self deactivatePiece];
}
	
#pragma mark -
#pragma mark Transaction
	
- (void)beginTransaction:(NSString*)aTtransactionName {
	if (transactionState != kAKDrawingTransactionIdle)
		[NSException raise:NSInternalInconsistencyException format:@"A transaction has already began or is running; cannot start another tranction."];
	if ([removingInfoSet count] != 0)
		[NSException raise:NSInternalInconsistencyException format:@"removingInfoSet is not empty"];
	transactionState = kAKDrawingTransactionBegan;
	transactionName = [aTtransactionName copy];
	animationsInTransaction = [[NSMutableSet alloc] init];
}
	
- (void)commitTransaction {
	if (transactionState == kAKDrawingTransactionBegan) {
		if ([animationsInTransaction count]) {
			transactionState = kAKDrawingTransactionRunning;
		} else {
			[self performSelectorOnMainThread:@selector(transactionEndAndNotifyDelegate) withObject:nil waitUntilDone:NO];
		}
	}
}

#pragma mark -
#pragma mark Helper Function
	
//	Translate the rect provided by geometryProvider to the view's coordinate system.
- (CGRect)rectInViewForRect:(CGRect)aRect {
	CGRect retVal;
	
	CGSize chessboard	= [geometryProvider chessboardSize];
#ifndef AKIPHONE
	NSRect viewSize		= [drawingView bounds];
#else
	CGRect viewSize		= drawingView.bounds;
#endif
	
	float radio;
	if (viewSize.size.width/viewSize.size.height > chessboard.width/chessboard.height)
		radio = viewSize.size.height / chessboard.height;
	else
		radio = viewSize.size.width / chessboard.width;
	
	retVal.size.width = aRect.size.width * radio;
	retVal.size.height = aRect.size.height * radio;
	retVal.origin.x = aRect.origin.x * radio;
	retVal.origin.y = aRect.origin.y * radio;
	
	return retVal;
}

//	Calculate position of a specified cell
- (CGRect)rectForCell:(AKCellIndex)cellIndex {
	CGRect rect			= [geometryProvider rectForCell:cellIndex];
	CGRect rectToDraw	= [self rectInViewForRect:rect];
	
	return rectToDraw;
}

#ifndef AKIPHONE
// Make the sublayers the same size as the super layer.
- (void)setToStickToSuperLayerForLayer:(CALayer*)aLayer {
	aLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
}

// Filter for activating pieces
- (CIFilter*)filterForActivation {
	CIFilter *filter = [CIFilter filterWithName:@"CIBloom"];
	[filter setDefaults];
	[filter setValue:[NSNumber numberWithFloat:5.0] forKey:@"inputRadius"];
	[filter setValue:[NSNumber numberWithFloat:0.0] forKey:@"inputIntensity"];
	[filter	setName:@"pulseFilter"];
	return filter;
}
#endif
	
- (void)transactionEndAndNotifyDelegate {
	transactionState = kAKDrawingTransactionIdle;
	[animationsInTransaction autorelease];
	[transactionName autorelease];
	[delegate transaction:transactionName didFinished:self];
}

#pragma mark -
#pragma mark Plug-in properties

- (NSString*)gameName	{ return AKPlugInAnyGame; }
- (NSString*)version	{ return AKMultimediaPlugInAnyVersion; }

@end

/** @} */
